package com.opensky.qatest.util.selenium;

import java.util.List;
import java.util.Set;

import junit.framework.Assert;

import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.opensky.qatest.util.common.AssertReport;

/*
 * WebDriverWrapper - wraps standard WebDriver methods
 * and adds external fixtures: such as wait for element present.
 * 
 *
 * @author: Aliakse_Khursevich (hursevich@gmail.com)
 * @version: 1.0
 * 
 */
public class WebDriverWrapper implements WebDriver, TakesScreenshot {

    // wait for element present for 25 second
    private static final long MAX_TIMEOUT = 50;

    // and retry checking every 1 second
    private static final long RETRY_TIME = 1;

    private Set<String> beforeHandles;

    static Wait<WebDriver> wait;

    WebDriver driver;

    public WebDriverWrapper(WebDriver driver) {
	this.driver = driver;
    }

    // ****************** Base wait before act methods ********************

    public boolean waitAndCheckElementPresent(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(AssertReport.elementNotFound(name));
	}
	return true;
    }

    public boolean waitAndVerifyElementPresent(final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    return false;
	}
	return true;
    }

    public boolean waitTimeoutAndCheckElementPresent(String name, final By by, long timeout) {

	wait = new WebDriverWait(driver, timeout, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    return false;
	}
	return true;
    }

    public boolean waitAndCheckElementNotPresent(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElements(by).size() == 0;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(AssertReport.unexpectedElementFound(name));
	}
	return true;
    }

    public WebElement waitAndGetElement(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(AssertReport.elementNotFound(name));
	}
	return driver.findElement(by);
    }

    public List<WebElement> waitAndGetElements(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElements(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(AssertReport.elementNotFound(name));
	}
	return driver.findElements(by);
    }

    public void waitAndClick(String name, final By by) {

	WebElement element = waitAndGetElement(name, by);
	element.click();
    }

    public void waitAndSendEnter(String name, final By by) {

	WebElement element = waitAndGetElement(name, by);
	element.sendKeys(Keys.ENTER);
    }

    public void waitAndSendKeys(String name, final By by, String keys) {

	WebElement element = waitAndGetElement(name, by);
	element.clear();
	element.sendKeys(keys);
    }

    public void waitAndHover(String name, final By by) {

	WebElement element = waitAndGetElement(name, by);
	Actions builder = new Actions(driver);
	builder.moveToElement(element).build().perform();
    }

    public boolean waitAndCheckURL(final String url) {
	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return driver.getCurrentUrl().contains(url);
		}
	    });
	} catch (Exception e) {
	    Assert.fail(AssertReport.wrongURL(url, driver.getCurrentUrl()));
	}
	return true;
    }

    public boolean waitAndVerifyURL(final String url) {
	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return driver.getCurrentUrl().contains(url);
		}
	    });
	} catch (Exception e) {
	    return false;
	}
	return true;
    }

    public void pause(long timeout) {
	try {
	    Thread.sleep(timeout * 1000);
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
    }

    public void saveBeforeHandles() {
	beforeHandles = driver.getWindowHandles();
    }

    public void switchToBottomWindow() {
	String newWindowHandle = beforeHandles.iterator().next();
	driver.switchTo().window(newWindowHandle);
    }

    public void switchToTopWindow() {
	Set<String> afterHandles = driver.getWindowHandles();
	afterHandles.removeAll(beforeHandles);
	String newWindowHandle = afterHandles.iterator().next();
	driver.switchTo().window(newWindowHandle);
    }

    // ****************** Javascript methods ********************

    public void maximizeWindow() {
	((JavascriptExecutor) driver)
		.executeScript("if (window.screen){window.moveTo(0, 0);window.resizeTo(window.screen.availWidth, window.screen.availHeight);};");
    }

    public void sielentAlert() {
	((JavascriptExecutor) driver).executeScript("window.alert = function(msg) { return true; }");
	((JavascriptExecutor) driver).executeScript("window.confirm = function(msg) { return true; }");
	((JavascriptExecutor) driver).executeScript("window.prompt = function(msg) { return true; }");
    }

    // ****************** WebDriver implemented methods ********************

    public void close() {
	driver.close();
    }

    public void goBack(int times) {
	for (int i = 0; i < times; i++) {
	    driver.navigate().back();
	}
    }

    public WebElement findElement(By locator) {
	return driver.findElement(locator);
    }

    public List<WebElement> findElements(By locator) {
	return driver.findElements(locator);
    }

    public void get(String url) {
	driver.get(url);
    }

    public String getCurrentUrl() {
	return driver.getCurrentUrl();
    }

    public String getPageSource() {
	return driver.getPageSource();
    }

    public String getTitle() {
	return driver.getTitle();
    }

    public String getWindowHandle() {
	return driver.getWindowHandle();
    }

    public Set<String> getWindowHandles() {
	return null;
    }

    public Options manage() {
	return driver.manage();
    }

    public Navigation navigate() {
	return driver.navigate();
    }

    public void quit() {
	driver.quit();
    }

    public TargetLocator switchTo() {
	return driver.switchTo();
    }

    public <X> X getScreenshotAs(OutputType<X> outType) throws WebDriverException {
	if (driver instanceof FirefoxDriver) {
	    return ((FirefoxDriver) driver).getScreenshotAs(outType);
	} else if (driver instanceof ChromeDriver) {
	    return ((ChromeDriver) driver).getScreenshotAs(outType);
	} else if (driver instanceof InternetExplorerDriver) {
	    return ((InternetExplorerDriver) driver).getScreenshotAs(outType);
	} else {
	    return null;
	}
    }
}
